﻿;; EVAL extends this stack trace when propagating exceptions.  If the
;; exception reaches the REPL loop, the full trace is printed.
(def! trace (atom ""))

;; read
(def! READ read-string)


;; eval
(def! EVAL (fn* [ast env]
  ;; (do (prn "EVAL:" ast))
  (try*
    (cond
      (symbol? ast)
      (let* [res (get env (str ast))]
        (if res res (throw (str ast " not found"))))

      (vector? ast)
      (vec (map (fn* [exp] (EVAL exp env)) ast))

      (map? ast)
      (apply hash-map
        (apply concat (map (fn* [k] [k (EVAL (get ast k) env)]) (keys ast))))

      (list? ast)
      (if (empty? ast)
        ()
        (let* [a0  (first ast)
               f    (EVAL a0 env)
               args (rest ast)]
          (if (fn? f)
            (apply f (map (fn* [exp] (EVAL exp env)) args))
            (throw "can only apply functions"))))

      "else"
      ast)

    (catch* exc
      (do
        (swap! trace str "\n  in lisp EVAL: " ast)
        (throw exc))))))

;; print
(def! PRINT pr-str)

;; repl
(def! repl-env {"+" +
                "-" -
                "*" *
                "/" /})
(def! rep (fn* [strng]
  (PRINT (EVAL (READ strng) repl-env))))

;; repl loop
(def! repl-loop (fn* [line]
  (if line
    (do
      (if (not (= "" line))
        (try*
          (println (rep line))
          (catch* exc
            (do
              (println "Uncaught exception:" exc @trace)
              (reset! trace "")))))
      (repl-loop (readline "lisp-user> "))))))

;; main
(repl-loop "")